﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WMM.Tapioca.Taxonomy;
using System.Xml.Schema;
using WMM.Tapioca.Instance;
using WMM.Tapioca.Linkbase;
using System.Xml;
using System.IO;
using System.Reflection;
using System.Xml.Linq;
using System.Collections;
using WMM.Tapioca.Utilities;

namespace WMM.Tapioca.Validate
{
    /// <summary>
    /// Class that validates the instance.
    /// </summary>
    public class ValidateInstance
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private DTS dts;

        private InstanceDocument xbrlInstance;

        public ValidateInstance(InstanceDocument instance)
        {
            dts = instance.Dts;
            xbrlInstance = instance;

            Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating instance..."));

            ValidateXbrlInstance();
            ValidateContextRefs();
            ValidateUnitRefs();
            ValidatePeriodTypeInConcept();
            ValidateFootnoteLocations();
            ValidateFootnoteResources();
            ValidateFootnoteArcs();
            ValidateItems();
            ValidateRoleRefAndArcRoleRef();

            ValidateDimensionalPrimaryItem vdpi = new ValidateDimensionalPrimaryItem(dts);

            Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs(""));
        }


        /// <summary>
        /// Validate XbrlInstance.
        /// </summary>
        private void ValidateXbrlInstance()
        {
            ValidationEventHandler eventHandler = new ValidationEventHandler(ValidationHandler);

            try
            {
                XmlSchemaSet xmlSchemaSet = new XmlSchemaSet();

                foreach (TaxonomySchema taxonomySchema in dts.TaxonomySchemas)
                {
                    //if (taxonomySchema.ParentDocument is InstanceDocument)
                    //{
                        VerifySchemasDuplicates(xmlSchemaSet, taxonomySchema.XmlSchemaSet);
                        xmlSchemaSet.Add(taxonomySchema.XmlSchemaSet);                       
                    //}
                }

                RemoveSchemaDuplicate(xmlSchemaSet);

                log.Info("Parsing instance: \"" + xbrlInstance.FileName + "\"");
                xbrlInstance.ThisXDocument.Validate(xmlSchemaSet, eventHandler, true);
            }
            catch (XmlSchemaValidationException ex)
            {
                log.Error("Validation error on instance", ex);
                dts.ValidatedInstance = false;
            }

            xbrlInstance.ReadContexts();
            xbrlInstance.ReadUnits();
            xbrlInstance.ReadFacts();
            xbrlInstance.ReadFootnoteLinks();
        }


        private void ValidationHandler(object sender, ValidationEventArgs e)
        {
            IXmlLineInfo lineInfo = ((sender as XObject) as IXmlLineInfo);

            log.Error("Validation error on instance, line " + lineInfo.LineNumber + ", position " + lineInfo.LinePosition + "." + e.Message);            
            //log.Error("Validation error on instance, line" + e.Exception.LineNumber + ", position " + e.Exception.LinePosition + ". " + e.Message);
            dts.ValidatedInstance = false;
        }


        private void VerifySchemasDuplicates(XmlSchemaSet xmlSchemaSetDTS, XmlSchemaSet xmlSchemaSet)
        {
            // Using an ArrayList lets use indexing for accessing the schemas.
            ArrayList schemaListDTS = new ArrayList(xmlSchemaSetDTS.Schemas());

            // ArrayList holding the duplicate schemas.
            ArrayList schemaList = new ArrayList(xmlSchemaSet.Schemas());

            for (int i = 0; i < schemaListDTS.Count; i++)
            {
                // Exhaustively test for duplicate schemas by means of the
                // SourceUri property.
                for (int j = i + 1; j < schemaList.Count; j++)
                {
                    if (((XmlSchema)schemaListDTS[i]).TargetNamespace == ((XmlSchema)schemaList[j]).TargetNamespace &&
                        ((XmlSchema)schemaListDTS[i]).SourceUri == ((XmlSchema)schemaList[j]).SourceUri) 
                    {
                        xmlSchemaSet.Remove((XmlSchema)schemaList[j]);
                    }
                }
            }
        }


        private static void RemoveSchemaDuplicate(XmlSchemaSet xmlSchemaSet)
        {
            // Using an ArrayList lets use indexing for accessing the schemas.
            ArrayList schemaList = new ArrayList(xmlSchemaSet.Schemas());

            // ArrayList holding the duplicate schemas.
            ArrayList duplicateSchemaList = new ArrayList();

            for (int i = 0; i < schemaList.Count; i++)
            {
                // Exhaustively test for duplicate schemas by means of the
                // SourceUri property.
                for (int j = i + 1; j < schemaList.Count; j++)
                {
                    if (((XmlSchema)schemaList[i]).TargetNamespace ==
                        ((XmlSchema)schemaList[j]).TargetNamespace)
                    {
                        //duplicateSchemaList.Add((XmlSchema)schemaList[j]);
                        xmlSchemaSet.Remove((XmlSchema)schemaList[j]);
                    }
                }
            }
        }


        /// <summary>
        /// Validate context references for all facts in the fragment.
        /// </summary>
        private void ValidateContextRefs()
        {
            ValidateContextRefs(xbrlInstance.Facts);
        }


        /// <summary>
        /// Validate context references for all facts in the given fact collection.
        /// </summary>
        /// <param name="factList">
        /// A collection of facts whose contexts should be validated.
        /// </param>
        private void ValidateContextRefs(List<Fact> factList)
        {
            foreach (Fact currentFact in factList)
            {
                if (currentFact is FactItem)
                {
                    ValidateContextRef(currentFact as FactItem);
                }
                else if (currentFact is WMM.Tapioca.Instance.FactTuple)
                {
                    var currentTuple = currentFact as WMM.Tapioca.Instance.FactTuple;
                    ValidateContextRefs(currentTuple.Facts);
                }
            }
        }


        //-------------------------------------------------------------------------------
        // Validates the context reference for the given fact. Ensures that the context
        // ref can be tied to a defined context.
        //-------------------------------------------------------------------------------
        private void ValidateContextRef(FactItem itemToValidate)
        {
            string contextRefValue = itemToValidate.ContextRefName;

            if (contextRefValue.Length == 0)
            {
                return;
            }

            bool contextFound = false;
            Context matchingContext = null;

            foreach (Context currentContext in xbrlInstance.Contexts)
            {
                if (currentContext.Id == contextRefValue)
                {
                    contextFound = true;
                    matchingContext = currentContext;
                    itemToValidate.ContextRef = matchingContext;
                }
            }

            if (!contextFound)
            {
                string messageFormat = AssemblyResources.GetName("CannotFindContextForContextRef");
                StringBuilder messageBuilder = new StringBuilder();
                messageBuilder.AppendFormat(messageFormat, contextRefValue);
                log.Error("Validation error on instance:" + messageBuilder.ToString());
                dts.ValidatedInstance = false;
            }
        }

       
        private void ValidateUnitRefs()
        {

            foreach (Fact currentFact in xbrlInstance.Facts)
            {
                if (currentFact is FactItem)
                {
                    ValidateUnitRef(currentFact as FactItem);
                    ValidateTypeUnitsRestrictions(currentFact as FactItem);
                }
                if (currentFact is WMM.Tapioca.Instance.FactTuple)
                {
                    ValidateUnitRefsInTuple(currentFact);
                }
            }
        }


        private void ValidateUnitRefsInTuple(Fact tuple)
        {            
            foreach (Fact tupleChild in (tuple as WMM.Tapioca.Instance.FactTuple).Facts)
            {
                if (tupleChild is FactItem)
                {
                    ValidateUnitRef(tupleChild as FactItem);
                    ValidateTypeUnitsRestrictions(tupleChild as FactItem);
                }
                else if (tupleChild is WMM.Tapioca.Instance.FactTuple)
                {
                    ValidateUnitRefsInTuple(tupleChild);
                }
            }
        }


        //-------------------------------------------------------------------------------
        // Validates the unit reference for the given fact. Ensures that the unit ref
        // can be tied to a defined unit.
        //-------------------------------------------------------------------------------
        private void ValidateUnitRef(FactItem itemToValidate)
        {
            string unitRefValue = itemToValidate.UnitRefName;
            //-----------------------------------------------------------------------
            // According to section 4.6.2, non-numeric items must not have a unit
            // reference. So, if the fact's unit reference is blank, and this is a
            // non-numeric item, then there is nothing to validate.
            //-----------------------------------------------------------------------
            if (unitRefValue.Length == 0)
            {
                if (itemToValidate.Concept == null)
                {
                    return;
                }

                if (itemToValidate.Type == null)
                {
                    return;
                }

                if (!itemToValidate.Type.IsNumeric())
                {
                    return;
                }
            }

            //-----------------------------------------------------------------------
            // At this point, we have a unit ref should be matched to a unit.
            //-----------------------------------------------------------------------
            bool unitFound = false;
            Unit matchingUnit = null;

            foreach (Unit currentUnit in xbrlInstance.Units)
            {
                if (currentUnit.Id == unitRefValue)
                {
                    unitFound = true;
                    matchingUnit = currentUnit;
                    itemToValidate.UnitRef = matchingUnit;
                }
            }

            //-----------------------------------------------------------------------
            // Check to see if a unit is found.
            //-----------------------------------------------------------------------
            if (!unitFound)
            {
                string messageFormat = AssemblyResources.GetName("CannotFindUnitForUnitRef");
                StringBuilder messageBuilder = new StringBuilder();
                messageBuilder.AppendFormat(messageFormat, unitRefValue);
                log.Error("Validation error on instance:" + messageBuilder.ToString());
                dts.ValidatedInstance = false;
            }
        }


        private void ValidateTypeUnitsRestrictions(FactItem itemToValidate)
        {
            //-----------------------------------------------------------------------
            //Measure reported with prefix undefined is considered XBRL invalid.
            //-----------------------------------------------------------------------
            string unitRefValue = itemToValidate.UnitRefName;

            foreach (Unit currentUnit in xbrlInstance.Units)
            {
                if (currentUnit.Id == unitRefValue)
                {
                    foreach (XName measure in currentUnit.Measures)
                    {
                        //if (itemToValidate.IsMonetary() && !measure.NamespaceName.Equals("http://www.xbrl.org/2003/iso4217"))
                        //{
                        //    log.Error("Validation error on instance:", new XbrlException("MonetaryItemType or derived from monetaryItemType, the namespace MUST be http://www.xbrl.org/2003/iso4217."));
                        //    dts.ValidatedInstance = false;
                        //}

                        if (itemToValidate.IsShares() && !measure.NamespaceName.Equals("http://www.xbrl.org/2003/instance"))
                        {
                            log.Error("Validation error on instance:", new XbrlException("SharesItemType or derived from sharesItemType, the namespace MUST be http://www.xbrl.org/2003/instance."));
                            dts.ValidatedInstance = false;
                        }
                    }
                }
            }
        }


        private void ValidatePeriodTypeInConcept()
        {
            foreach (Fact currentFact in xbrlInstance.Facts)
            {
                if (currentFact is FactItem)
                {
                    FactItem currentItem = currentFact as FactItem;

                    switch (currentItem.Concept.PeriodType)
                    {
                        case Concept.EnumPeriodType.Duration:

                            if ((currentItem.ContextRef != null) && !currentItem.ContextRef.IsDurationPeriod)
                            {
                                StringBuilder messageBuilder = new StringBuilder();
                                string stringFormat = AssemblyResources.GetName("ElementSchemaDefinesDurationButUsedWithNonDurationContext");
                                messageBuilder.AppendFormat(stringFormat, currentItem.Concept.Schema.PathFile, currentItem.Name, currentItem.ContextRef.Id);
                                log.Error("Validation error on instance:" + messageBuilder.ToString());
                                dts.ValidatedInstance = false;
                            }
                            break;

                        case Concept.EnumPeriodType.Instant:

                            if ((currentItem.ContextRef != null) && !currentItem.ContextRef.IsInstantPeriod)
                            {
                                StringBuilder messageBuilder = new StringBuilder();
                                string stringFormat = AssemblyResources.GetName("ElementSchemaDefinesInstantButUsedWithNonInstantContext");
                                messageBuilder.AppendFormat(stringFormat, currentItem.Concept.Schema.PathFile, currentItem.Name, currentItem.ContextRef.Id);
                                log.Error("Validation error on instance:" + messageBuilder.ToString());
                                dts.ValidatedInstance = false;
                            }
                            break;
                    }
                }
            }
        }


        //-------------------------------------------------------------------------------
        // Validate all of the facts found in the instance. Multiple activities happen
        // here:
        //
        // * each fact is validated against its data type described in its definition
        //   via an <element> tag in a taxonomy schema
        // * any facts that participate in an arc role are checked
        //-------------------------------------------------------------------------------
        private void ValidateItems()
        {
            foreach (Fact currentFact in xbrlInstance.Facts)
            {
                if (currentFact is FactItem)
                {
                    var currentItem = currentFact as FactItem;
                    if (currentItem != null)
                    {
                        currentItem.Validate();
                        ValidateNilItem(currentItem);
                    }
                }
                else
                {
                    var currentTuple = currentFact as WMM.Tapioca.Instance.FactTuple;
                    if (currentTuple != null)
                        currentTuple.Validate();
                }
            }

            ValidateFactsReferencedInDefinitionArcRoles();
        }


        //-------------------------------------------------------------------------------
        // Searches the given XBRL schemas, looking for facts that are referenced
        // in arc roles.
        //-------------------------------------------------------------------------------
        private void ValidateFactsReferencedInDefinitionArcRoles()
        {
            foreach (LinkbaseDocument currentLinkbaseDocument in dts.LinkbaseDocuments)
            {
                if (currentLinkbaseDocument.RoleLinkbaseRef.Equals("http://www.xbrl.org/2003/role/definitionLinkbaseRef"))
                {
                    ValidateFactsReferencedInDefinitionArcRoles(currentLinkbaseDocument);
                }
            }

        }


        //-------------------------------------------------------------------------------
        // Searches the given linkbase document, looking for facts that are referenced
        // in arc roles.
        //-------------------------------------------------------------------------------
        private void ValidateFactsReferencedInDefinitionArcRoles(LinkbaseDocument currentLinkbaseDocument)
        {
            foreach (ExtendedLink CurrentDefinitionLink in currentLinkbaseDocument.ExtendedLinks)
            {
                ValidateFactsReferencedInDefinitionArcRoles(CurrentDefinitionLink);
            }
        }

        
        private void ValidateFactsReferencedInDefinitionArcRoles(ExtendedLink currentDefinitionLink)
        {
            foreach (Arc CurrentDefinitionArc in currentDefinitionLink.Arcs)
            {
                switch (CurrentDefinitionArc.ArcRole.AbsoluteUri)
                {
                    case Constants.DEFINITIONEssenceAliasArcrole:
                        ValidateEssenceAliasedFacts(CurrentDefinitionArc);
                        break;
                    case Constants.DEFINITIONRequiresElementArcrole:
                        ValidateRequiresElementFacts(CurrentDefinitionArc);
                        break;
                    default:
                        break;
                }
            }
        }


        //-------------------------------------------------------------------------------
        // Validate the "requires element" connection between two facts referenced in a
        // definition arc.
        //-------------------------------------------------------------------------------
        private void ValidateRequiresElementFacts(Arc requiresElementDefinitionArc)
        {
            Locator currentFromLocator = requiresElementDefinitionArc.FromLocator;
            Locator currentToLocator = requiresElementDefinitionArc.ToLocator;
            int fromFactCount = CountFactInstances(currentFromLocator.HrefResourceId);
            int toFactCount = CountFactInstances(currentToLocator.HrefResourceId);
            if (fromFactCount > toFactCount)
            {
                StringBuilder messageBuilder = new StringBuilder();
                string stringFormat = AssemblyResources.GetName("NotEnoughToFactsInRequiresElementRelationship");
                messageBuilder.AppendFormat(stringFormat, currentFromLocator.HrefResourceId, currentToLocator.HrefResourceId);
                log.Error("Validation error on instance:" + messageBuilder.ToString());
                dts.ValidatedInstance = false;
            }
        }


        //-------------------------------------------------------------------------------
        // Returns a count of the number of facts with the given name.
        //-------------------------------------------------------------------------------
        private int CountFactInstances(string factName)
        {
            int count = 0;

            foreach (Fact currentFact in xbrlInstance.Facts)
            {
                if (currentFact.Name.Equals(factName))
                {
                    count++;
                }
            }

            return count;
        }


        /// <summary>
        /// Validate nil item.
        /// </summary>
        private void ValidateNilItem(FactItem itemToValidate)
        {
            if (!itemToValidate.Type.IsNumeric())
            {
                return;
            }

            if (itemToValidate.UnitRef != null)
            {
                if (!itemToValidate.IsNilSpecified && !itemToValidate.IsPrecisionSpecified && !itemToValidate.IsDecimalsSpecified)
                {
                    log.Error("Validation error on item: " + itemToValidate.Name, new XbrlException("nil items have no decimals or precision, with unitref, but the type specifies fixed values for decimals and precision."));
                    dts.ValidatedInstance = false;
                }
            }
        }


        private void ValidateFootnoteArcs()
        {
            foreach (ExtendedLink currentFootnoteLink in xbrlInstance.FootnoteLinks)
            {
                foreach (Arc currentArc in currentFootnoteLink.Arcs)
                {
                    ValidateFootnoteArc(currentFootnoteLink, currentArc);
                }
            }
        }

        
        private void ValidateFootnoteArc(ExtendedLink currentFootnoteLink, Arc currentArc)
        {
            if (currentArc.ArcRole.Equals("http://www.xbrl.org/2003/arcrole/fact-footnote"))
            {
                Locator locator = null;
                ExtendedLinkElement linkElement = GetExtendedLinkElement(currentFootnoteLink, currentArc.FromId);

                if (linkElement != null && linkElement.IsResource())
                {
                    log.Error("Validation error on instance", new XbrlException("The element \"from\" can not be a resource"));
                    dts.ValidatedInstance = false;
                }
                else
                {
                    if (linkElement != null)
                    {
                        locator = (Locator)linkElement;
                    }
                }

                if (locator == null)
                {
                    StringBuilder messageBuilder = new StringBuilder();
                    string stringFormat = AssemblyResources.GetName("CannotFindFootnoteLocator");
                    messageBuilder.AppendFormat(stringFormat, currentArc.Title, currentArc.FromId);
                    log.Error("Validation error on instance:" + messageBuilder.ToString());
                    dts.ValidatedInstance = false;
                }
                else
                {
                    if ((locator.HrefDocumentUri.Length > 0) && (!UrlReferencesFragmentDocument(locator.HrefDocumentUri)))
                    {
                        StringBuilder messageBuilder = new StringBuilder();
                        string stringFormat = AssemblyResources.GetName("FootnoteReferencesFactInExternalDoc");
                        messageBuilder.AppendFormat(stringFormat, locator.HrefResourceId, locator.HrefDocumentUri);
                        log.Error("Validation error on instance:" + messageBuilder.ToString());
                        dts.ValidatedInstance = false;
                    }

                    Fact fromID = GetFact(locator.HrefResourceId);

                    if (fromID == null)
                    {
                        StringBuilder messageBuilder = new StringBuilder();
                        string stringFormat = AssemblyResources.GetName("CannotFindFactForFootnoteArc");
                        messageBuilder.AppendFormat(stringFormat, currentArc.Title, locator.Href);
                        log.Error("Validation error on instance:" + messageBuilder.ToString());
                        dts.ValidatedInstance = false;
                    }
                }

                ExtendedLinkElement toID = GetExtendedLinkElement(currentFootnoteLink, currentArc.ToId);

                if (toID != null && toID.IsLocator())
                {
                    log.Error("Validation error on instance", new XbrlException("The element \"to\" can not be a locator"));
                    dts.ValidatedInstance = false;
                }

                if (toID == null)
                {
                    StringBuilder messageBuilder = new StringBuilder();
                    string stringFormat = AssemblyResources.GetName("CannotFindFootnoteForFootnoteArc");
                    messageBuilder.AppendFormat(stringFormat, currentArc.Title, currentArc.ToId);
                    log.Error("Validation error on instance:" + messageBuilder.ToString());
                    dts.ValidatedInstance = false;
                }
            }
            else
            {
                //The footnote custom arc role can relate two footnotes to each other instead of just fact to footnote. 
                ExtendedLinkElement fromId = GetExtendedLinkElement(currentFootnoteLink, currentArc.FromId);

                if (fromId == null)
                {
                    StringBuilder messageBuilder = new StringBuilder();
                    string stringFormat = AssemblyResources.GetName("CannotFindFootnoteForFootnoteArc");
                    messageBuilder.AppendFormat(stringFormat, currentArc.Title, currentArc.ToId);
                    log.Error("Validation error on instance:" + messageBuilder.ToString());
                    dts.ValidatedInstance = false;
                }

                ExtendedLinkElement toID = GetExtendedLinkElement(currentFootnoteLink, currentArc.ToId);

                if (toID == null)
                {
                    StringBuilder messageBuilder = new StringBuilder();
                    string stringFormat = AssemblyResources.GetName("CannotFindFootnoteForFootnoteArc");
                    messageBuilder.AppendFormat(stringFormat, currentArc.Title, currentArc.ToId);
                    log.Error("Validation error on instance:" + messageBuilder.ToString());
                    dts.ValidatedInstance = false;
                }
            }

        }


        private ExtendedLinkElement GetExtendedLinkElement(ExtendedLink extendedLink, string label)
        {
            foreach (Locator currentLocator in extendedLink.Locators)
            {
                if (currentLocator.Label.Equals(label))
                {
                    return currentLocator;
                }
            }

            foreach (Footnote currentFootnote in extendedLink.Resources)
            {
                if (currentFootnote.Label.Equals(label))
                {
                    return currentFootnote;
                }
            }

            return null;
        }

        
        private bool UrlReferencesFragmentDocument(string hrefUri)
        {
            if (hrefUri.Length == 0)
            {
                return false;
            }

            string docFullPath = GetPath(xbrlInstance.PathFile);

            string hrefFullPathString;

            if (hrefUri.IndexOf(Path.DirectorySeparatorChar) == -1)
            {
                hrefFullPathString = xbrlInstance.Path + Path.DirectorySeparatorChar + hrefUri;
            }
            else
            {
                hrefFullPathString = hrefUri;
            }

            string hrefFullPath = GetPath(hrefFullPathString);

            if (docFullPath.Equals(hrefFullPath))
            {
                return true;
            }

            return false;
        }


        private void ValidateFootnoteLocations()
        {
            foreach (ExtendedLink currentFootnoteLink in xbrlInstance.FootnoteLinks)
            {
                foreach (Locator currentLocation in currentFootnoteLink.Locators)
                {
                    ValidateFootnoteLocation(currentLocation.Href);   
                }
            }
        }


        private void ValidateFootnoteLocation(string footnoteLocationReference)
        {
            HyperlinkReference reference = new HyperlinkReference(footnoteLocationReference);

            if (reference.UrlSpecified && reference.Url != xbrlInstance.FileName)
            {
                StringBuilder messageBuilder = new StringBuilder();
                string stringFormat = AssemblyResources.GetName("FootnoteReferencesFactInExternalDoc");
                messageBuilder.AppendFormat(stringFormat, reference.ElementId, reference.Url);
                log.Error("Validation error on instance:" + messageBuilder.ToString());
                dts.ValidatedInstance = false;
            }

            if (GetFact(reference.ElementId) == null)
            {
                StringBuilder messageBuilder = new StringBuilder();
                string stringFormat = AssemblyResources.GetName("NoFactForFootnoteReference");
                messageBuilder.AppendFormat(stringFormat, footnoteLocationReference);
                log.Error("Validation error on instance:" + messageBuilder.ToString());
                dts.ValidatedInstance = false;
            }
        }


        private void ValidateFootnoteResources()
        {
            foreach (ExtendedLink currentFootnoteLink in xbrlInstance.FootnoteLinks)
            {
                foreach (Resource currentResource in currentFootnoteLink.Resources)
                {
                    if (currentResource.Lang == null)
                    {
                        StringBuilder messageBuilder = new StringBuilder();
                        string stringFormat = AssemblyResources.GetName("NoLangForFootnote");
                        messageBuilder.AppendFormat(stringFormat, currentResource.Label);
                        log.Error("Validation error on footnote", new XbrlException(messageBuilder.ToString()));
                        dts.ValidatedInstance = false;
                    }
                }
            }
        }


        /// <summary>
        /// Returns a reference to the fact having the supplied fact ID.
        /// </summary>
        /// <param name="factId">
        /// The ID of the fact to return.
        /// </param>
        /// <returns>
        /// A reference to the fact having the supplied fact ID.
        /// A null is returned if no facts with the supplied fact ID is available.
        /// </returns>
        public Fact GetFact(string factId)
        {
            foreach (Fact currentFact in xbrlInstance.Facts)
            {
                if (currentFact.Id == factId)
                {
                    return currentFact;
                }
            }

            return null;
        }


        private string GetPath(string uriBase)
        {
            string path = uriBase;
            int LastPathSeparator = uriBase.LastIndexOf('/');

            if (LastPathSeparator == -1)
            {
                LastPathSeparator = uriBase.LastIndexOf('\\');
                if (LastPathSeparator != -1)
                {
                    path = uriBase.Substring(0, LastPathSeparator + 1);
                }
            }

            path = uriBase.Substring(0, LastPathSeparator + 1);
            return path;
        }

        //===============================================================================
        #region Definition Arc Role Validation
        //===============================================================================

        /// <summary>
        /// Validate the essence alias between two facts referenced in a definition arc using
        /// the set of all facts in the fragment. 
        /// </summary>
        /// <param name="essenceAliasDefinitionArc">
        /// The definition arc defining the essence alias.
        /// </param>
        private void ValidateEssenceAliasedFacts(Arc essenceAliasDefinitionArc)
        {
            ValidateEssenceAliasedFacts(essenceAliasDefinitionArc, xbrlInstance.Facts);
        }


        /// <summary>
        /// Validate the essence alias between two facts referenced in a definition arc using
        /// the set of all facts in the fragment. 
        /// </summary>
        /// <param name="essenceAliasDefinitionArc">
        /// The definition arc defining the essence alias.
        /// </param>
        /// <param name="FactList">
        /// A collection of <see cref="Fact"/> objects defined in the fragment.
        /// </param>
        private void ValidateEssenceAliasedFacts(Arc essenceAliasDefinitionArc, List<Fact> FactList)
        {
            Concept fromConcept = dts.GetConceptByID(essenceAliasDefinitionArc.FromLocator.HrefResourceId);

            if (fromConcept == null)
            {
                throw new NullReferenceException("fromConcept is NULL in ValidateEssenceAliasedFacts()");
            }

            Concept toConcept = dts.GetConceptByID(essenceAliasDefinitionArc.ToLocator.HrefResourceId);

            foreach (Fact CurrentFact in FactList)
            {
                if (CurrentFact is FactItem)
                {
                    FactItem CurrentItem = CurrentFact as FactItem;

                    if (CurrentItem.Name.Equals(fromConcept.Name))
                    {
                        ValidateEssenceAliasedFacts(CurrentItem, FactList, toConcept.Name);
                    }
                }
                else if (CurrentFact is WMM.Tapioca.Instance.FactTuple)
                {
                    var CurrentTuple = CurrentFact as WMM.Tapioca.Instance.FactTuple;
                    ValidateEssenceAliasedFacts(essenceAliasDefinitionArc, CurrentTuple.Facts);
                }
            }
        }


        /// <summary>
        /// Validate the essence alias between a given fact and all other facts with the given fact name.
        /// </summary>
        /// <remarks>
        /// <para>
        /// An essence alias is a relationship between a "from" item and a "to" item. The "from" item and
        /// the "to" item must be identical. This method is given the "from" item and must search for the
        /// corresponding "to" item.
        /// </para>
        /// <para>
        /// The scoping of the search for the corresponding "to" item is important. In the simple case, an
        /// XBRL fragment has only items, and the search for the corresponding "to" item can be conducted
        /// in the list all of all items in the fragment.
        /// </para>
        /// <para>
        /// However, if the "from" item is found in a tuple, then the list of items from which the "to" item
        /// is to be found must be restricted to the other items in the tuple and not simply the set of all
        /// items in the fragment.
        /// </para>
        /// </remarks>
        /// <param name="EssenceItem">
        /// The item that represents the "from" end of the essence alias relationship.
        /// </param>
        /// <param name="factList">
        /// The list of facts that should be searched to find the item that represents the "to" end of the essence alias relationship.
        /// </param>
        /// <param name="aliasItemName">
        /// The name of the item that represents the "to" end of the essence alias relationship.
        /// </param>
        private void ValidateEssenceAliasedFacts(FactItem EssenceItem, List<Fact> factList, string aliasItemName)
        {
            foreach (Fact currentFact in factList)
            {
                var aliasItem = currentFact as FactItem;

                if (aliasItem != null)
                {
                    if (currentFact.Name.Equals(aliasItemName))
                    {
                        ValidateEssenceAliasedFacts(EssenceItem, aliasItem);
                    }
                }
            }
        }


        //-------------------------------------------------------------------------------
        // Validate the essence alias between two given facts.
        //-------------------------------------------------------------------------------
        private void ValidateEssenceAliasedFacts(FactItem essenceItem, FactItem aliasItem)
        {

            // Essence alias checks for c-equals items are a bit tricky, according to the
            // XBRL-CONF-CR3-2007-03-05 conformance suite. Test 392.11 says that it is valid
            // to have two items with contexts having the same structure but different
            // period values is valid; however, test 392.13 says that it is invalid two have
            // two items with contexts having a different structure.

            if (!essenceItem.ContextEquals(aliasItem))
            {
                if ((essenceItem.ContextRef != null) && (aliasItem.ContextRef != null))
                {
                    if (!(essenceItem.Concept.PeriodTypeString.Equals(aliasItem.Concept.PeriodTypeString) &&
                        essenceItem.Concept.BalanceString.Equals(aliasItem.Concept.BalanceString)))
                    {
                        StringBuilder messageBuilder = new StringBuilder();
                        string StringFormat = AssemblyResources.GetName("EssenceAliasFactsNotContextEquals");
                        messageBuilder.AppendFormat(StringFormat, essenceItem.Name, aliasItem.Name, essenceItem.Id, aliasItem.Id);
                        log.Error("Validation error on instance:" + messageBuilder.ToString());
                        dts.ValidatedInstance = false;
                    }
                }

                return;
            }

            if (!essenceItem.ParentEquals(aliasItem))
            {
                StringBuilder messageBuilder = new StringBuilder();
                string StringFormat = AssemblyResources.GetName("EssenceAliasFactsNotParentEquals");
                messageBuilder.AppendFormat(StringFormat, essenceItem.Name, aliasItem.Name, essenceItem.Id, aliasItem.Id);
                log.Error("Validation error on instance:" + messageBuilder.ToString());
                dts.ValidatedInstance = false;
            }

            if (!essenceItem.UnitEquals(aliasItem))
            {
                StringBuilder messageBuilder = new StringBuilder();
                string StringFormat = AssemblyResources.GetName("EssenceAliasFactsNotUnitEquals");
                messageBuilder.AppendFormat(StringFormat, essenceItem.Name, aliasItem.Name, essenceItem.Id, aliasItem.Id);
                log.Error("Validation error on instance:" + messageBuilder.ToString());
                dts.ValidatedInstance = false;
            }

            // At this point, the valies of the items need to be compared. Check the item's type
            // to ensure that the correct value is being compared.

            bool isItemValuesMatch = true;

            if (essenceItem.Concept.TypeName.Name.Equals("stringItemType"))
            {
                isItemValuesMatch = essenceItem.Value.Equals(aliasItem.Value);
            }
            else
            {
                if (essenceItem.RoundedValue != aliasItem.RoundedValue)
                {
                    isItemValuesMatch = false;
                }
            }

            if (!isItemValuesMatch)
            {
                StringBuilder messageBuilder = new StringBuilder();
                string StringFormat = AssemblyResources.GetName("EssenceAliasFactsHaveDifferentRoundedValues");
                messageBuilder.AppendFormat(StringFormat, essenceItem.Name, aliasItem.Name, essenceItem.Id, essenceItem.RoundedValue.ToString(), aliasItem.Id, aliasItem.RoundedValue.ToString());
                log.Error("Validation error on instance:" + messageBuilder.ToString());
                dts.ValidatedInstance = false;
            }
        }


        private void ValidateRoleRefAndArcRoleRef()
        {
            //RoleRef
            foreach (RoleRef roleRef in dts.RoleRefs)
            {
                if (roleRef.ParentDocument is InstanceDocument)
                {
                    string roleURI = roleRef.roleUri;

                    foreach (RoleRef roleRefToCompare in dts.RoleRefs)
                    {
                        if (roleRefToCompare != roleRef)
                        {
                            if (roleRefToCompare.roleUri.Equals(roleURI))
                            {
                                log.Error("Validation error: Instance contains two role references to the same URI: " + roleURI);
                                dts.ValidatedInstance = false;
                                break;
                            }
                        }
                    }
                }
            }

            //ArcRoleRef
            foreach (ArcRoleRef arcRoleRef in dts.ArcRoleRefs)
            {
                if (arcRoleRef.parentDocument is InstanceDocument)
                {
                    string arcRoleURI = arcRoleRef.arcroleURI;

                    foreach (ArcRoleRef arcRoleRefToCompare in dts.ArcRoleRefs)
                    {
                        if (arcRoleRefToCompare != arcRoleRef)
                        {
                            if (arcRoleRefToCompare.arcroleURI.Equals(arcRoleURI))
                            {
                                log.Error("Validation error: Instance contains two arcRole references to the same URI: " + arcRoleURI);
                                dts.ValidatedInstance = false;
                                return;
                            }
                        }
                    }
                }
            }
        }
        //===============================================================================
        #endregion
        //===============================================================================
    }
}
